package com.huitong.common.mybatis.interceptor;

import cn.hutool.core.util.ArrayUtil;
import com.huitong.common.mybatis.annotation.AutoFill;
import com.huitong.common.mybatis.domain.po.BaseFieldPO;
import com.huitong.common.satoken.utils.LoginUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.EnumSet;

/**
 * 自动填充公共字段拦截器
 *
 * @author fromdrowning
 * @date 2024/3/24 15:19
 */
@Slf4j
@Intercepts(
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
)
public class AutoFillInterceptor implements Interceptor {

    /**
     * 触发自动填充公共字段sql类型集合
     */
    private static final EnumSet<SqlCommandType> autoFillSqlType = EnumSet.of(SqlCommandType.INSERT, SqlCommandType.UPDATE);

    /**
     * 创建者学号公共字段名
     */
    private static final String CREATE_BY = "createBy";

    /**
     * 创建时间公共字段名
     */
    private static final String CREATE_TIME = "createTime";

    /**
     * 修改者学号公共字段名
     */
    private static final String UPDATE_BY = "updateBy";

    /**
     * 修改时间公共字段名
     */
    private static final String UPDATE_TIME = "updateTime";

    /**
     * 自动填充公共字段
     *
     * @param invocation 被拦截方法的详细信息
     * @return 拦截处理后方法的返回值
     * @throws Throwable 被拦截方法运行出现异常抛出此异常
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();

        // 不是指定sql操作直接返回
        if (!autoFillSqlType.contains(((MappedStatement) args[0]).getSqlCommandType())) {
            return invocation.proceed();
        }

        Object domainPO = args[1];
        Class<?> superclass = domainPO.getClass()
            .getSuperclass();

        // 找到PO对象的父类BaseFieldPO并进行公共字段的填充
        while (superclass != null && superclass != Object.class) {
            if (superclass == BaseFieldPO.class) {
                autoFillFields(superclass.getDeclaredFields(), domainPO);
                break;
            }
            superclass = superclass.getSuperclass();
        }

        return invocation.proceed();
    }

    /**
     * 填充公共字段
     * <p>
     * 判断字段是否被AutoFill注解标识并且是指定sql操作才进行共字段的填充
     *
     * @param fields   {@link BaseFieldPO}对象内的所有字段
     * @param domainPO 需要填充公共字段的PO对象
     */
    private void autoFillFields(Field[] fields, Object domainPO) {
        for (Field field : ArrayUtil.filter(fields, field -> field.isAnnotationPresent(AutoFill.class))) {
            EnumSet<SqlCommandType> fieldSqlTypes = EnumSet.copyOf(Arrays.asList(field.getAnnotation(AutoFill.class).value()));
            if (autoFillSqlType.stream().anyMatch(fieldSqlTypes::contains)) {
                field.setAccessible(true);
                try {
                    switch (field.getName()) {
                        case CREATE_BY, UPDATE_BY:
                            field.set(domainPO, LoginUtils.getLoginUserId());
                            break;
                        case CREATE_TIME, UPDATE_TIME:
                            field.set(domainPO, LocalDateTime.now());
                            break;
                    }
                } catch (IllegalAccessException e) {
                    log.error("自动填充公共字段失败", e);
                }
            }
        }
    }
}
